package com.alone.mybatisplus.datascope.core;

import cn.hutool.core.text.CharSequenceUtil;
import com.alone.mybatisplus.datascope.annotation.Scope;
import com.alone.mybatisplus.datascope.core.domain.DataScope;
import com.alone.mybatisplus.datascope.core.domain.ModelDataScope;
import com.alone.mybatisplus.datascope.core.domain.NoHandleDataScope;
import com.alone.mybatisplus.datascope.core.domain.SqlDataScope;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.MappedStatement;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * 数据权限提取器<p/>
 * 依次从缓存->参数->方法注解->类注解检索数据权限信息
 *
 * @author Alone
 */
@Slf4j
@UtilityClass
public class DataScopeExtractor {

    private final Map<MappedStatement, Supplier<DataScope>> DATA_SCOPE_CACHE = new HashMap<>();

    /**
     * 查找参数是否包括DataScope对象
     *
     * @param parameterObj 参数列表
     * @param ms           映射语句
     * @return DataScope
     */
    public DataScope findDataScope(Object parameterObj, MappedStatement ms) {
        DataScope res;
        if ((res = getByCache(ms)) != null) {
            return res;
        }
        if ((res = getByParam(parameterObj)) != null) {
            putCache(ms, res);
            return res;
        }
        if ((res = getByAnnotation(ms)) != null) {
            putCache(ms, res);
            return res;
        }
        putCache(ms, NoHandleDataScope.INSTANCE);
        return NoHandleDataScope.INSTANCE;
    }

    private DataScope getByCache(MappedStatement ms) {
        Optional<Supplier<DataScope>> dataScopeSupplier = Optional.ofNullable(DATA_SCOPE_CACHE.get(ms));
        if (dataScopeSupplier.isPresent()) {
            DataScope dataScope = dataScopeSupplier.get().get();
            log.debug("DATA_SCOPE_CACHE命中, key:{}, val:{}", ms.getId(), dataScope);
            return dataScope;
        }
        return null;
    }

    private DataScope getByParam(Object parameterObj) {
        if (parameterObj instanceof DataScope) {
            return (DataScope) parameterObj;
        }

        if (parameterObj instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) parameterObj;
            for (Object val : map.values()) {
                if (val instanceof DataScope) {
                    return (DataScope) val;
                }
            }
        }
        return null;
    }

    @SneakyThrows
    private DataScope getByAnnotation(MappedStatement ms) {
        String id = ms.getId();
        String className = id.substring(0, id.lastIndexOf("."));
        final Class<?> mapperClass = Class.forName(className);
        String methodName = id.substring(id.lastIndexOf(".") + 1);
        final Method[] method = mapperClass.getMethods();
        Scope scope;
        // 从方法中查找
        for (Method me : method) {
            if (me.getName().equals(methodName)
                    && (scope = AnnotationUtils.findAnnotation(me, Scope.class)) != null) {
                return mapByAnnotation(scope);
            }
        }
        // 从类上查找
        scope = AnnotationUtils.findAnnotation(mapperClass, Scope.class);
        return mapByAnnotation(scope);
    }

    private DataScope mapByAnnotation(Scope scope) {
        if (scope == null || scope.allowAll()) {
            return NoHandleDataScope.INSTANCE;
        }
        if (CharSequenceUtil.isNotBlank(scope.sql())) {
            return SqlDataScope.of(scope.sql());
        }
        return ModelDataScope.of(scope.model());
    }

    private void putCache(MappedStatement ms, DataScope ds) {
        DATA_SCOPE_CACHE.put(ms, () -> ds);
    }
}
